{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<center>\n",
    "<img src=\"../../img/ods_stickers.jpg\">\n",
    "## Открытый курс по машинному обучению. Сессия № 2\n",
    "\n",
    "### <center> Автор материала: Константин Измайлов"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Данный проект будет посвящен анализу текучки ценных сотрудников компании, данные взяты из датасета от компании. IBM\n",
    "Сам датасет можно найти на [kaggle](https://www.kaggle.com/pavansubhasht/ibm-hr-analytics-attrition-dataset)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Анализ оттока сотрудников компании"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 1. Описание набора данных и признаков"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<B> Ценность исследования. </B>На сегодняшний день персонал представляет собой один из основных ресурсов фирмы, управление и развитие которого во многом определяет состояние организации в целом. Работа с ним требует эффективной политики руководства, по сути, уникальной для каждого предприятия.\n",
    "\n",
    "Одной из проблем работы с персоналом является текучесть кадров. Она может привести к упущению прибыли, потере квалифицированных рабочих, снижению качества продукции, росту изделий с браком и увеличению числа прогулов. Чтобы контролировать увольнения и понять, как удержать лучших сотрудников, необходимо определить причины, по которым люди покидают компанию.\n",
    "\n",
    "В случае, если уход сотрудников из компании неизбежен, то необходимо взять данный процесс под контроль и суметь вовремя среагировать, чтобы минимизировать потери компании."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<B>Процесс сбора данных.</B> Датасет был сгенерирован искусственно сотрудниками компании IBM."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<B>Целевая переменная.</B> Целевая переменная представляет собой флаг ухода сотрудника из компании."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<B>Описание признаков.</B> Датасет состоит из целевой переменной (Attrition), 34 признаков, включащих как числовые, так и категориальные признаки, всего в датасете - 1470 объектов.\n",
    "\n",
    "<I>Age</I> -  Возраст(Numeric)\n",
    "<I>Attrition</I> -  Флаг ухода из компании(String)\n",
    "<I>BusinessTravel</I> -  Частота командировок(String)\n",
    "<I>DailyRate</I> - Ставка в день в долларах(Numeric)\n",
    "<I>Department</I> - Департамент(String)\n",
    "<I>DistanceFromHome</I> -  Расстояние от дома до работы(Numeric)\n",
    "<I>Education</I> -  Образование(Numeric)\n",
    "<I>EducationField</I> -  Специальность(String)\n",
    "<I>EmployeeCount</I> - Число подчиненных(Numeric)\n",
    "<I>EmployeeNumber</I> - (Numeric)\n",
    "<I>EnvironmentSatisfaction</I> -  Удовлетворенность обстановкой в компании(Numeric)\n",
    "<I>Gender</I> -  Пол(String)\n",
    "<I>HourlyRate</I> - Время работы(Numeric)\n",
    "<I>JobInvolvement</I> -  Вовлеченность в работу(Numeric)\n",
    "<I>JobRole</I> -  Должность(String)\n",
    "<I>JobSatisfaction</I> - Удовлетворенность работй(Numeric)\n",
    "<I>MaritalStatus</I> - Семейное положение(String)\n",
    "<I>MonthlyIncome</I> - Доход в месяц(Numeric)\n",
    "<I>MonthlyRate</I> - Ставка в месяц(Numeric)\n",
    "<I>NumCompaniesWorked</I> - Число компаний, где работал сотрудник до текущей компании(Numeric)\n",
    "<I>Over18</I> - Больше 18 лет(String)\n",
    "<I>OverTime</I> - Сверхурочное время(String)\n",
    "<I>PercentSalaryHike</I> - Надбавка к заработной плате(Numeric)\n",
    "<I>PerformanceRating</I> - Рейтинг производительности(Numeric)\n",
    "<I>RelationshipSatisfaction</I> - Удовлетворение отношением внутри коллектива(Numeric)\n",
    "<I>StandardHours</I> - Стандартный график работы(Numeric)\n",
    "<I>StockOptionLevel</I> - Сколько акций комании есть у сотрудника(Numeric)\n",
    "<I>TotalWorkingYears</I> - Продолжительность трудового стажа(Numeric)\n",
    "<I>TrainingTimesLastYear</I> - Общее время посещения тренировочных семинаров на работе(Numeric)\n",
    "<I>WorkLifeBalance</I> - Соотношение свободного времени и работы(Numeric)\n",
    "<I>YearsAtCompany</I> - Продолжительность работы в компании(Numeric)\n",
    "<I>YearsInCurrentRole</I> - Продолжительность работы на текущей позиции(Numeric)\n",
    "<I>YearsSinceLastPromotion</I> - Сколько прошло с последнего повышения(Numeric)\n",
    "<I>YearsWithCurrManager</I> - Продолжительность работы с текущим менеджмером(Numeric)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 2. Первичный анализ данных"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import pandas as pd\n",
    "import seaborn as sns\n",
    "from matplotlib import pyplot\n",
    "import seaborn\n",
    "import numpy as np\n",
    "%matplotlib inline"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "data = pd.read_csv('WA_Fn-UseC_-HR-Employee-Attrition.csv')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Посмотрим на данные."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "data.head(2)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Уже сейчас, исходя только из названия признаков, можно предположить, что в датасете имеются признаки с сильной линейной взаимосвязью."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Проверим на пропуски в данных."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "data.info()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "В данных нет пропусков.\n",
    "\n",
    "Посмотрим на данные более подробно, предварительно задав максимальное число столбцов для отображения в pandas."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pd.options.display.max_columns = 100\n",
    "data.describe()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Можно заметить, что некоторые признаки являются константами, их можно удалить из исследуемого датасета."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "data_nc = data.drop(['EmployeeCount', 'StandardHours'],axis =1 )\n",
    "data_nc.shape"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Исследуем распределение целевой переменной в датасете."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "data_nc['Attrition'].value_counts()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Видим, что переменная целевого признака распределена неравномерно.\n",
    "\n",
    "Изменим тип целевой переменной на бинарный, выделим числовые признаки в отдельную переменную."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "attrition_dic = {'Yes':1, 'No':0}\n",
    "data_nc[\"Attrition\"] = data_nc[\"Attrition\"].apply(lambda x: attrition_dic[x])\n",
    "\n",
    "numerical = [u'Age', u'DailyRate', u'DistanceFromHome', u'Education', u'EmployeeNumber', u'EnvironmentSatisfaction',\n",
    "       u'HourlyRate', u'JobInvolvement', u'JobLevel', u'JobSatisfaction',\n",
    "       u'MonthlyIncome', u'MonthlyRate', u'NumCompaniesWorked',\n",
    "       u'PercentSalaryHike', u'PerformanceRating', u'RelationshipSatisfaction',\n",
    "       u'StockOptionLevel', u'TotalWorkingYears',\n",
    "       u'TrainingTimesLastYear', u'WorkLifeBalance', u'YearsAtCompany',\n",
    "       u'YearsInCurrentRole', u'YearsSinceLastPromotion',\n",
    "       u'YearsWithCurrManager']\n",
    "\n",
    "non_numerical = [u'BusinessTravel',u'Department',\n",
    " u'EducationField',\n",
    " u'Gender',\n",
    " u'JobRole',\n",
    " u'MaritalStatus', u'OverTime']"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Сильной диспропорции между признаками нет, также можно из датасета убрать признак Over18 - константа."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "data_nc = data_nc.drop(['Over18'],axis =1 )"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 3. Первичный визуальный анализ данных"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Исследуем корреляцию Пирсона числовых признаков."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "a4_dims = (11.7, 8.27)\n",
    "df = data_nc[numerical].corr()\n",
    "fig, ax = pyplot.subplots(figsize=a4_dims)\n",
    "seaborn.heatmap(ax=ax, data=df)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Между признаком JobLevel и MonthlyIncome - высокая линейная взаимосвязь, что логично. Чем выше уровень работы сотрудника, тем больше месячный доход. Соответсвенно, один из признаков, возможно, является избыточным, при обучении модели попробуем в дальнейшем удалить его для повышения качества модели.\n",
    "\n",
    "Теперь посмотрим на распределение признаков."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "nc_count = len(numerical)\n",
    "_, axes = pyplot.subplots(nc_count, 1, figsize=(16, nc_count * 5))\n",
    "\n",
    "for i, col in enumerate(numerical):\n",
    "    sns.distplot(data_nc[col], ax=axes[i], color = 'gray');"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "nc_count = len(non_numerical[1:])\n",
    "_, axes = pyplot.subplots(nc_count, 1, figsize=(16, nc_count * 5))\n",
    "\n",
    "for i, col in enumerate(non_numerical[1:]):\n",
    "    sns.countplot(data_nc[col], ax=axes[i], color = 'lightgray');"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Так как в качестве основной модели мы будем использовать деревья, соответсвенно, в нормализации признаков нет необходимости."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Из полученных распределений интересно обратить внимание на показатель DailyRate - распредлен равномерно, то есть работодатели анализируемой компании страются равномерно распределять дневную ставку оплаты труда среди всех сотрудников, тоже самое можно сказать и про почасовую оплату труда (HourlyRate), и про месячную оплату труду (MonthlyRate). Такое распределение можно объяснить искусственной природой данных.\n",
    "\n",
    "Из полученных данных можно предположить, что компания работает в сфере социальных исследований. "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 4. Инсайты, найденные зависимости"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Закодируем все числовые признаки через OHE и затем попробуем найти взаимосвязь отдельных признаков с целевой переменной."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "bt = pd.get_dummies(data_nc['BusinessTravel'])\n",
    "dp = pd.get_dummies(data_nc['Department'])\n",
    "ef = pd.get_dummies(data_nc['EducationField'])\n",
    "g = pd.get_dummies(data_nc['Gender'])\n",
    "ot = pd.get_dummies(data_nc['OverTime'])\n",
    "jr = pd.get_dummies(data_nc['JobRole'])\n",
    "ms = pd.get_dummies(data_nc['MaritalStatus'])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "data_nc_feat = data_nc[numerical]\n",
    "#Целевой признак\n",
    "data_nc_feat['Attrition'] = data_nc['Attrition']\n",
    "data_nc_feat = pd.concat([data_nc_feat, bt, dp, ef, g,ot,jr,ms], axis=1)\n",
    "\n",
    "data_nc_feat.head(2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def plot_distribution(df, var, target, **kwargs):\n",
    "    row = kwargs.get('row', None)\n",
    "    col = kwargs.get('col', None)\n",
    "    facet = sns.FacetGrid(df, hue=target, aspect=4, row=row, col=col)\n",
    "    facet.map(sns.kdeplot, var, shade=True)\n",
    "    facet.set(xlim=(0, df[var].max()))\n",
    "    facet.add_legend()\n",
    "    pyplot.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plot_distribution(data_nc_feat, 'Age', 'Attrition')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Видно, что большая часть людей увольняется в возрасте 30 лет."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plot_distribution(data_nc_feat, 'DailyRate', 'Attrition')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Сотрудники со ставкой в день до 800 более склонны к увольнению."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plot_distribution(data_nc_feat, 'DistanceFromHome', 'Attrition')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Также из распределения видно, что сотрудники более склонны к увольнению, если они живут более 10 км от работы."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plot_distribution(data_nc_feat, 'Education', 'Attrition')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plot_distribution(data_nc_feat, 'EnvironmentSatisfaction', 'Attrition')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Отчетливо видно, что удовлетворенность обстановкой в коллективе сильно влияет на уход из компании."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plot_distribution(data_nc_feat, 'JobInvolvement', 'Attrition')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plot_distribution(data_nc_feat, 'JobSatisfaction', 'Attrition')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Удовлетворенность работой также влияет на отток сотрудников."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plot_distribution(data_nc_feat, 'NumCompaniesWorked', 'Attrition')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Сотрудники, которые много раз поменяли работу - более склонны к оттоку."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plot_distribution(data_nc_feat, 'TotalWorkingYears', 'Attrition')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Сотрудники, проработавшие в компании более 7 лет менее склонны к оттоку."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plot_distribution(data_nc_feat, 'YearsSinceLastPromotion', 'Attrition')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Горб на 7 годе говорит о том, что сотрудники ожидают повышение через 7 лет, в противном случае веротяность их оттока из компании возрастает."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plot_distribution(data_nc_feat, 'YearsWithCurrManager', 'Attrition')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Сотруднику необходимо проработать с одним менеджером не менее 2 лет, чтобы вероятность его оттока из компании уменьшилась."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plot_distribution(data_nc_feat, 'WorkLifeBalance', 'Attrition')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Здесь однозначных закономерностей не видно."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plot_distribution(data_nc_feat, 'TrainingTimesLastYear', 'Attrition')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Интересно, что отток больше у сотрудников, которые не посещали тренировочыне семинар за последний год, а также у сотрудников, которые посещали 4 семинара, вероятно, повысив свою квалификацию, решили найти место получше."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 5. Выбор метрики"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "В качестве основной метрики для оценки качества модели будет выбран критерий ROC-AUC. Как уже было замечено, данная задача имеет высокую несбалансированность целевого класса, в свою очередь, метрика ROC AUC зависят от предсказанных вероятностей классов, а не от «жёсткой» дискретной классификации."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 6. Выбор модели"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Стоит задача бинарной классификации: определить, уволится ли сотрудник или нет.\n",
    "В качестве моделей выберем:\n",
    "    - RandomForestClassifier\n",
    "    - XGBoostClassifier\n",
    "    \n",
    "Данные модели не требуют нормализацию признаков, в том числе исходный набор данных обладает небольшим количеством объектов и признаков, также видна нелинейная зависимость между целевой переменной и отдельными признаками, что было наглядно представлено в пункте 4. Это еще больше подтверждает выбор решающих деревьев в качестве основной модели. \n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 7. Предобработка данных"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Необходимая предобработка данных была проведена в прочих пунктах данной работы:\n",
    "    - закодированы категориальные признаки;\n",
    "    - убраны признаки, ялвяющиеся константами;\n",
    "    - выбраны признаки с высокой корреляцией, в дальнейшем, возможно, их стоит исключить из модели;\n",
    "    - далее на этапе подбора гиперпараметров модели будет оценено качество моделей при дополнении исходной выборки синтетическими данными."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 8. Кросс-валидация и настройка гиперпараметров модели"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Для начала корректно разобьем наш исходный датасет на трейн и тест, на тесте мы оценим финальное качество алгоритма.\n",
    "Разбивать будем Stratified, так как целевой класс несбалансирован. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "data_nc_feat.columns.values[31] = 'Human Resources 2'\n",
    "data_nc_feat.columns.values[42] = 'Human Resources 3'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from sklearn.cross_validation import train_test_split\n",
    "\n",
    "X_train, X_test, y_train, y_test = train_test_split(data_nc_feat.drop('Attrition', axis = 1), np.array(data_nc_feat['Attrition']), test_size=0.15,  random_state=42, stratify=np.array(data_nc_feat['Attrition']))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print('Доля положителных меток для трейна: {}'.format(sum(y_train)/X_train.shape[0]))\n",
    "print('Доля положителных меток для теста: {}'.format(sum(y_test)/X_test.shape[0]))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from sklearn.ensemble import RandomForestClassifier\n",
    "from xgboost import XGBClassifier\n",
    "from sklearn.model_selection import GridSearchCV,RandomizedSearchCV"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from operator import itemgetter\n",
    "\n",
    "def report(grid_scores, n_top):\n",
    "    top_scores = sorted(grid_scores, key=itemgetter(1), reverse=True)[:n_top]\n",
    "    for i, score in enumerate(top_scores):\n",
    "        print(\"Model with rank: {0}\".format(i + 1))\n",
    "        print(\"Mean validation score: {0:.4f})\".format(\n",
    "              score.mean_validation_score,\n",
    "              np.std(score.cv_validation_scores)))\n",
    "        print(\"Parameters: {0}\".format(score.parameters))\n",
    "        print(\"\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Проведем первоначальное приближение моделей с рандомизированным побором гиперпараметров на кросс-валидации по 3 фолдам с помощью RandomizedSearchCV. "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ограничимся основными гиперпараметрами для RandomForestClassifier:\n",
    "<UL>\n",
    "    <LI>n_estimators - число деревьев\n",
    "    <LI>min_samples_split - минимальное число объектов, при котором выполняется расщепление\n",
    "    <LI>min_samples_leaf - ограничение на число объектов в листьях\n",
    "    <LI>max_depth - максимальная глубина дерева\n",
    "    <LI>criterion - критерий расщипления\n",
    "    \n",
    "    </UL>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "param_grid_for_forest = { \"n_estimators\": np.arange(10, 300, 2),\n",
    "                          \"max_depth\": np.arange(1, 20, 1),\n",
    "                          \"min_samples_split\": np.arange(2,150,1),\n",
    "                          \"min_samples_leaf\": np.arange(2,60,1),\n",
    "                          \"max_features\" : np.arange(1,X_train.shape[1],1)}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "grid_search = RandomizedSearchCV(RandomForestClassifier(),error_score='roc-auc', param_distributions=param_grid_for_forest, cv = 3,verbose = 1,random_state = 40)\n",
    "grid_search.fit(X_train, y_train)\n",
    "\n",
    "report(grid_search.grid_scores_, 3)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Аналогично и для XGBoostClassifier, для гиперпараметров: n_estimators, max_depth, subsample, colsample_bytree, reg_alpha,gamma.   "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "param_grid_for_xgb = {\"n_estimators\": np.arange(10, 300, 2),\n",
    "                      \"max_depth\": np.arange(1,20,1),\n",
    "                      \"subsample\" : [i/10.0 for i in range(1,10)],\n",
    "                      'colsample_bytree':[i/10.0 for i in range(1,10)],\n",
    "                      'reg_alpha':[0,1e-5, 1e-2, 0.1, 1, 100]    ,\n",
    "                      'gamma' : [i/10.0 for i in range(1,10)]}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "grid_search = RandomizedSearchCV(XGBClassifier(), error_score='roc-auc',param_distributions=param_grid_for_xgb, cv = 3,verbose = 1,random_state = 40)\n",
    "grid_search.fit(X_train, y_train)\n",
    "\n",
    "report(grid_search.grid_scores_, 3)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Уже можно заметить, что XGBClassifier обладает чуть лучшим качеством.\n",
    "\n",
    "Попробуем сгенерировать дополнительные синтетические объекты для минорного класса уволившихся сотрудников и оценим качество получившихся моделей, взяв параметры моделей из RandomizedSearchCV. Стоит обратить внимание, что при оценке качества модели с учетом новых синтетических объектов данные на тесте должны остаться в прежнем виде."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from sklearn.model_selection import StratifiedKFold,ShuffleSplit\n",
    "from imblearn.over_sampling import SMOTE,ADASYN \n",
    "from sklearn.metrics import roc_auc_score\n",
    "skf = StratifiedKFold(n_splits=5, random_state = 40)\n",
    "scores_rf_synth = []\n",
    "scores_xgb_synth = []\n",
    "scores_rf = []\n",
    "scores_xgb = []\n",
    "for train, test in skf.split(X_train, y_train):\n",
    "    smote=SMOTE(random_state=40)\n",
    "    smote_train, smote_target = SMOTE().fit_sample(X_train.iloc[train],y_train[train])\n",
    "    \n",
    "    #RF\n",
    "    model_RF = RandomForestClassifier(random_state = 40,n_estimators = 242, min_samples_split = 62, min_samples_leaf = 30, max_features = 45, max_depth = 14)\n",
    "    model_RF.fit(X_train.iloc[train],y_train[train])\n",
    "    predictions = model_RF.predict_proba(X_train.iloc[test])[:,1]\n",
    "    scores_rf.append(roc_auc_score(y_train[test],predictions))\n",
    "    \n",
    "    model_RF.fit(smote_train,smote_target)\n",
    "    predictions = model_RF.predict_proba(X_train.iloc[test])[:,1]\n",
    "    scores_rf_synth.append(roc_auc_score(y_train[test],predictions))\n",
    "    \n",
    "    #XGB\n",
    "    model_XGB = XGBClassifier(subsample = 0.4, reg_alpha = 1, n_estimators = 52, max_depth = 10, gamma = 0.7, colsample_bytree = 0.9)\n",
    "    model_XGB.fit(X_train.iloc[train],y_train[train])\n",
    "    predictions = model_XGB.predict_proba(X_train.iloc[test])[:,1]\n",
    "    scores_xgb.append(roc_auc_score(y_train[test],predictions))\n",
    "    \n",
    "    model_XGB.fit(smote_train,smote_target)\n",
    "    predictions = model_XGB.predict_proba(X_train.iloc[test].as_matrix())[:,1]\n",
    "    scores_xgb_synth.append(roc_auc_score(y_train[test],predictions))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print('Средний ROC-AUC для RF - {}'.format(np.mean(scores_rf)))\n",
    "print('Средний ROC-AUC для синтетического RF - {}'.format(np.mean(scores_rf_synth)))\n",
    "print('Средний ROC-AUC для XGB - {}'.format(np.mean(scores_xgb)))\n",
    "print('Средний ROC-AUC для синтетического XGB - {}'.format(np.mean(scores_xgb_synth)))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Добавление синтетических данных чуть ухудшило нашу модель. Для обучения модели будем использовать XGBoost на исходном датасете."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Теперь попробуем более точно подобрать гиперпараметры модели."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "param_grid_for_xgb2 = {\"n_estimators\": [100,300,500],\n",
    "              \"max_depth\": [2,5,10],\n",
    "                       \"subsample\": [0.3,0.6,0.9],\n",
    "                       \"colsample_bytree\":[0.3,0.6,0.9],\n",
    "                       'reg_alpha':[0, 1e-2, 0.1, 1],\n",
    "              \"gamma\": [0.3,0.6,0.9]\n",
    "              }"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "grid_search = GridSearchCV(XGBClassifier(), param_grid=param_grid_for_xgb2, cv = 3,verbose = 1,error_score='roc-auc')\n",
    "grid_search.fit(X_train, y_train)\n",
    "\n",
    "report(grid_search.grid_scores_, 3)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "model_xgb = XGBClassifier(subsample = 0.3,reg_alpha = 0, n_estimators = 300, max_depth = 2, gamma = 0.6, colsample_bytree = 0.3)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 9. Создание новых признаков и описание этого процесса"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Вместо создания новых признаков, уберем из модели незначительные признаки и оценим качество модели."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Попробуем оценить качество модели, выкинув незначимые для модели признаки."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "feat_importance = pd.DataFrame(X_train.columns, columns = ['features'])\n",
    "feat_importance['value'] = grid_search.best_estimator_.feature_importances_\n",
    "feat_importance.sort_values('value')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Наибольшую важность в модели имеют признаки, связанные с денежным вознаграждением сотрудников, возрастом."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "drops_1 = feat_importance.sort_values('value')['features'][:20]\n",
    "drops_2 = feat_importance.sort_values('value')['features'][:30]\n",
    "drops_3 = feat_importance.sort_values('value')['features'][:10]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "skf = StratifiedKFold(n_splits=4, random_state = 40)\n",
    "scores_ = []\n",
    "for train, test in skf.split(X_train, y_train):\n",
    "    model_xgb.fit(X_train.iloc[train],y_train[train])\n",
    "    predictions = model_xgb.predict_proba(X_train.iloc[test])[:,1]\n",
    "    scores_.append(roc_auc_score(y_train[test],predictions))\n",
    "print('ROC-AUC на исходном трейне - {}'.format(np.mean(scores_)))    "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "scores_ = []\n",
    "for train, test in skf.split(X_train.drop(drops_1, axis = 1), y_train):\n",
    "    model_xgb.fit(X_train.drop(drops_1, axis = 1).iloc[train],y_train[train])\n",
    "    predictions = model_xgb.predict_proba(X_train.drop(drops_1, axis = 1).iloc[test])[:,1]\n",
    "    scores_.append(roc_auc_score(y_train[test],predictions))\n",
    "print('ROC-AUC 1 - {}'.format(np.mean(scores_))) "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "scores_ = []\n",
    "for train, test in skf.split(X_train.drop(drops_2, axis = 1), y_train):\n",
    "    model_xgb.fit(X_train.drop(drops_2, axis = 1).iloc[train],y_train[train])\n",
    "    predictions = model_xgb.predict_proba(X_train.drop(drops_2, axis = 1).iloc[test])[:,1]\n",
    "    scores_.append(roc_auc_score(y_train[test],predictions))\n",
    "print('ROC-AUC 2 - {}'.format(np.mean(scores_))) "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "scores_ = []\n",
    "for train, test in skf.split(X_train.drop(drops_3, axis = 1), y_train):\n",
    "    model_xgb.fit(X_train.drop(drops_3, axis = 1).iloc[train],y_train[train])\n",
    "    predictions = model_xgb.predict_proba(X_train.drop(drops_3, axis = 1).iloc[test])[:,1]\n",
    "    scores_.append(roc_auc_score(y_train[test],predictions))\n",
    "print('ROC-AUC 3 - {}'.format(np.mean(scores_))) "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "После того, как убрали из модели 10 малозначительных признаков, качество модели немного улучшилось."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Уберем из модели признак JobLevel, который имел высокую корреляцию с признаком MonthlyIncome."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "scores_ = []\n",
    "for train, test in skf.split(X_train.drop(drops_3, axis = 1).drop('JobLevel', axis = 1), y_train):\n",
    "    model_xgb.fit(X_train.drop(drops_3, axis = 1).drop('JobLevel', axis = 1).iloc[train],y_train[train])\n",
    "    predictions = model_xgb.predict_proba(X_train.drop(drops_3, axis = 1).drop('JobLevel', axis = 1).iloc[test])[:,1]\n",
    "    scores_.append(roc_auc_score(y_train[test],predictions))\n",
    "print('ROC-AUC 3 - {}'.format(np.mean(scores_))) "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Нагенерим много новых искусственных признаков из полученного датасета без малозначимых признаков."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X_train_generated = X_train.drop(drops_3, axis = 1).copy()\n",
    "print('\\nBefore transformation: ', X_train.drop(drops_3, axis = 1).shape)\n",
    "columns = X_train_generated.columns\n",
    "\n",
    "for i1, col1 in enumerate(columns):\n",
    "    for i2, col2 in enumerate(columns):\n",
    "        if col1 == col2:\n",
    "            X_train_generated['%s_%s_0' % (col1, col2)] = np.log(X_train[col1] + 1)     \n",
    "        \n",
    "        X_train_generated['%s_%s_1' % (col1, col2)] = X_train[col1] / (X_train[col2] + 1)\n",
    "        X_train_generated['%s_%s_2' % (col1, col2)] = X_train[col1] * X_train[col2]\n",
    "print('\\nAfter transformation: ', X_train_generated.shape)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Немного потюним признаки и еще раз оценим качество получившейся модели."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Исходный датасет без малозначимых\n",
    "grid_search = RandomizedSearchCV(XGBClassifier(), param_distributions=param_grid_for_xgb, cv = 3,verbose = 1, random_state = 10)\n",
    "grid_search.fit(X_train_generated, y_train)\n",
    "\n",
    "report(grid_search.grid_scores_, 3)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Качество модели только ухудшилось.\n",
    "\n",
    "Попробуем отобрать признаки с помощью алгоритма Boruta. Данный алгоритм статистически корректно выбирает фичи из деревьев по их важности."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from boostaroota import BoostARoota\n",
    "br = BoostARoota(clf=model_xgb)\n",
    "new_train = br.fit_transform(X_train_generated, y_train)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "new_train.shape"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Алгоритм оставил 325 признаков. Оценим качество получившейся модели."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "grid_search = RandomizedSearchCV(XGBClassifier(), param_distributions=param_grid_for_xgb, cv = 3,verbose = 1, random_state = 22)\n",
    "grid_search.fit(new_train, y_train)\n",
    "\n",
    "report(grid_search.grid_scores_, 3)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Качество модели меняется незначительно. Оставим датасет с меньшим количеством признаков."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Теперь более точно пройдемся по сетке и сравним качество модели с гиперпарамтерами, подобранными на 8 шаге. Дали ли манипуляции с добавлением признаков улучшение качества."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "grid_search = GridSearchCV(XGBClassifier(), param_grid=param_grid_for_xgb2, cv = 3,verbose = 1,error_score='roc-auc')\n",
    "grid_search.fit(new_train, y_train)\n",
    "\n",
    "report(grid_search.grid_scores_, 3)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Качество модели улучшилось. Добавление признаков дало значимый прирост в качестве."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 10. Построение кривых валидации и обучения"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Построим кривые."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from sklearn.model_selection import learning_curve\n",
    "def plot_learning_curve(estimator, title, X, y, ylim=None, cv=None,\n",
    "                        n_jobs=1, train_sizes=np.linspace(.1, 1.0, 5)):\n",
    "    pyplot.figure()\n",
    "    pyplot.title(title)\n",
    "    if ylim is not None:\n",
    "        pyplot.ylim(*ylim)\n",
    "    pyplot.xlabel(\"Training examples\")\n",
    "    pyplot.ylabel(\"Score\")\n",
    "    train_sizes, train_scores, test_scores = learning_curve(\n",
    "        estimator, X, y, cv=cv, n_jobs=n_jobs, train_sizes=train_sizes)\n",
    "    train_scores_mean = np.mean(train_scores, axis=1)\n",
    "    train_scores_std = np.std(train_scores, axis=1)\n",
    "    test_scores_mean = np.mean(test_scores, axis=1)\n",
    "    test_scores_std = np.std(test_scores, axis=1)\n",
    "    pyplot.grid()\n",
    "\n",
    "    pyplot.fill_between(train_sizes, train_scores_mean - train_scores_std,\n",
    "                     train_scores_mean + train_scores_std, alpha=0.1,\n",
    "                     color=\"r\")\n",
    "    pyplot.fill_between(train_sizes, test_scores_mean - test_scores_std,\n",
    "                     test_scores_mean + test_scores_std, alpha=0.1, color=\"g\")\n",
    "    pyplot.plot(train_sizes, train_scores_mean, 'o-', color=\"r\",\n",
    "             label=\"Training score\")\n",
    "    pyplot.plot(train_sizes, test_scores_mean, 'o-', color=\"g\",\n",
    "             label=\"Cross-validation score\")\n",
    "\n",
    "    pyplot.legend(loc=\"best\")\n",
    "    return pyplot\n",
    "\n",
    "\n",
    "cv = StratifiedKFold(n_splits=5, random_state=5)\n",
    "estimator = grid_search.best_estimator_\n",
    "plot_learning_curve(estimator, 'Кривые валидации и обучения', new_train, y_train, ylim=(0.0, 1.01), cv=cv, n_jobs=8)\n",
    "\n",
    "\n",
    "pyplot.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Модель достаточно хорошо сходится. Хотя, добавить новых признаков все же могло еще улучшить сходимость."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 11. Прогноз для тестовой или отложенной выборки "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Приведем отложенный датасет к надлежащему виду, повторив все преобразования."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X_test_generated = X_test.drop(drops_3, axis = 1).copy()\n",
    "print('\\nBefore transformation: ', X_test.drop(drops_1, axis = 1).shape)\n",
    "columns = X_test_generated.columns\n",
    "\n",
    "for i1, col1 in enumerate(columns):\n",
    "    for i2, col2 in enumerate(columns):\n",
    "        if col1 == col2:\n",
    "            X_test_generated['%s_%s_0' % (col1, col2)] = np.log(X_test[col1] + 1)     \n",
    "        \n",
    "        X_test_generated['%s_%s_1' % (col1, col2)] = X_test[col1] / (X_test[col2] + 1)\n",
    "        X_test_generated['%s_%s_2' % (col1, col2)] = X_test[col1] * X_test[col2]\n",
    "print('\\nAfter transformation: ', X_test_generated.shape)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "new_test = br.transform(X_test_generated)\n",
    "new_test.shape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "m = grid_search.best_estimator_\n",
    "m.fit(new_train,y_train)\n",
    "roc_auc_score(y_test,m.predict_proba(new_test)[:, 1])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Качество на отложенный выборке сопоставимо со средним качеством на кросс-валидации."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 12. Выводы "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "По итогу была построена модель, предсказывающая отток сотрудников из компании. При анализе было выявлено, что наибольшое влияние на отток сотрудников из компании имеют признаки, связанные с оплатой труда и возрастом.\n",
    "\n",
    "Полученное качество модели, оцененное с поомщью метрики ROC-AUС составляет 0.83, что говорит о высокой предсказательной способности модели. \n",
    "\n",
    "Использовать данную модель могут спецаилисты HR-отдела, понимая, какой сотрудник с какой вероятностью может покинуть компанию, тем самым минимизируя кадровые риски компании."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Возможные улучшения:\n",
    "\n",
    "- точнее отобрать значимые для модели признаки\n",
    "- попробовать использовать дополнительные внешние данные, хоть ии на искусственном датасете это сделать нельзя (вуз, который закончил сотрудник, город)\n",
    "- оценить, где алгоритм ошибается больше всего и скорректировать модель."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Спасибо за внимание!"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
